#include "precompiled.h"
#include "Game.h"
#include "UI_Manager.h"
#include "Sound_Manager.h"
#include "RenderManager.h"
#include "ParticleManager.h"

#define PATH_MONKEY "../models/BLEND/mySuzanne.blend"
#define TEST_WORLD		"../worlds/wtest_1.world"

void Game::load_defaultAssets(){
	// Load default assets.
	gs_assetIO.load_model_to_pool(PATH_MONKEY,"monkey");
	gs_assetIO.load_model_plane( "plane" );

	ShaderPool::PoolShaders();
}


void Game::checkFBOCompleteleness(){

	GLenum fboStat = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if(fboStat != GL_FRAMEBUFFER_COMPLETE_EXT){

		switch(fboStat){

		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
			printError("FBO ERROR: ","GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT...");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
			printError("FBO ERROR: ","GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT...");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
			printError("FBO ERROR: ","GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT...");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
			printError("FBO ERROR: ","GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT...");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
			printError("FBO ERROR: ","GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT...");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
			printError("FBO ERROR: ","GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT...");
			break;
		case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
			printError("FBO ERROR: ","GL_FRAMEBUFFER_UNSUPPORTED_EXT...");
			break;
		default:
			printError("FBO ERROR: ","Incomplete...");
			break;
		}
	}
}

void Game::Dbg_setup_fbo(){
	int TEXTURE_WIDTH = 512;
	int TEXTURE_HEIGHT = 512;

	// create a texture object
	glGenTextures(1, &colorbo[0]);
	glBindTexture(GL_TEXTURE_2D, colorbo[0]);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap generation included in OpenGL v1.4
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
	glBindTexture(GL_TEXTURE_2D, 0);
	tCB.set_texID(colorbo[0]);

	// create a framebuffer object, you need to delete them when program exits.
	glGenFramebuffersEXT(1, &fbo[0]);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo[0]);

	// create a renderbuffer object to store depth info
	// NOTE: A depth renderable image should be attached the FBO for depth test.
	// If we don't attach a depth renderable image to the FBO, then
	// the rendering output will be corrupted because of missing depth test.
	// If you also need stencil test for your rendering, then you must
	// attach additional image to the stencil attachement point, too.
	glGenRenderbuffersEXT(1, &depth_rb[0]);
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth_rb[0]);
	glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

	// attach a texture to FBO color attachement point
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, colorbo[0], 0);

	// attach a renderbuffer to depth attachment point
	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth_rb[0]);

	//@ disable color buffer if you don't attach any color buffer image,
	//@ for example, rendering depth buffer only to a texture.
	//@ Otherwise, glCheckFramebufferStatusEXT will not be complete.
	//glDrawBuffer(GL_NONE);
	//glReadBuffer(GL_NONE);

	// check FBO status
	checkFBOCompleteleness();

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}


Game::Game(void)
{
	running=false;
}


Game::~Game(void)
{
	// Destroy all scenes.
	gs_sceneStack->Destroy();
}



void Game::initialize(){


	t0 = 0.0;
	tElapsed=0.0;
	tLast=0.0;
	frames = 0;

	// Initialize GLFW.
	glfwInit();

	// Open the OpenGL window.
	if( !glfwOpenWindow( 640, 480, 8,8,8,8,32,0,GLFW_WINDOW)){
		// glfwOpenWindow failed, so quit the program.
		glfwTerminate();

		std::cout << "GLFW failed to open the glWindow.. Terminating." << std::endl;
		std::cin.get();

	}
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);

	/************************************************************************/


	// Load the extensions for GLSL - note that this has to be done
	// *after* the window has been opened, or we won't have a GL context
	// to query for those extensions and connect to instances of them.
	loadExtensions();

	// Create pools.
	Create_Pools();

	// Start our Awesomium UI.
	gs_UIMan = new UI_Manager(640,480);

	// Create shaders.
	ShaderPool::PoolShaders();
	ShaderPool::SetShader( ShaderPool::Shaders::NORMALDIFFUSE );


	// Create our FBO stuff.
	Dbg_setup_fbo();

	// Enable back face culling and Z buffering.
	glEnable(GL_CULL_FACE); // Cull away all back facing polygons.
	glEnable(GL_DEPTH_TEST); // Use the Z buffer.

	// Set the clear color.
	glClearColor(0.0f,0.3f,0.6f,1.0f);

	ShaderPool::update_uniform_MVP_View();

	load_defaultAssets();
	gs_sceneStack->Load_FSQ();
	gs_sceneStack->Push_On_Scene( new Scene(SceneType::InGame) );

	SoundTBB_Run();
	PhysicsTBB_Run();
}
void Game::cleanup(){
	Destroy_Pools();
	PhysicsManager::Cleanup_Havok();
}

void Game::LoadWorld(){}

void Game::run(){

	int running = GL_TRUE;

	while(running){


		//******************************************************
		// Check if the ESC key was pressed or if the window was closed.
		if( glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED)){
			running=GL_FALSE;
		}
		// Check to see if we should reload our shaders.
		else if( glfwGetKey(GLFW_KEY_ENTER) ){
			ShaderPool::reload_shaders();
		}
		// Update our UI input.
		//ui_man->GetInput();

		// Update our camera rotation.
		projview = gs_sceneStack->GetCurrentCamera();

		// Calculate and update the frames per second (FPS) display.
		showFPS();

		// Give our shader program the time.
		ShaderPool::update_uniform_time( t0 );

		// Clear the color and depth buffer.
		//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		// Update the scene.
		
		gs_sceneStack->Update_CurrentScene( tElapsed );
		//******************************************************

		//******************************************************
		glEnable(GL_BLEND); //Enable alpha blending
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //Set the blend function


		// Draw our scene.
		//Dbg_render_with_fbo();
		//Dbg_render_normal();
		gs_sceneStack->Submit_CurrentScene( *projview );
		ParticleManager::SubmitParticleTickets();
		RendererManager::Draw( *projview );

		// Update/Draw our UI.
		//ui_man->UpdateDraw();

		// Swap buffers, i.e. display the image and prepare for next frame.
		glfwSwapBuffers();
		//******************************************************

		//if( !gamepaused )
		RendererManager::Clear_Tickets();
	}

	cleanup();

	// Close the OpenGL window and terminate GLFW.
	glfwTerminate();
}

/*
 * loadExtensions() - Load OpenGL extensions for anything above OpenGL
 * We need to load functionality from extensions... Mandatory stuff!
 */
void Game::loadExtensions() {
    //These extension strings indicate that the OpenGL Shading Language,
    // version 1.00, and GLSL shader objects are supported.
    if(!glfwExtensionSupported("GL_ARB_shading_language_100"))
    {
        printError("GL init error", "GL_ARB_shading_language_100 extension was not found");
        return;
    }
    if(!glfwExtensionSupported("GL_ARB_shader_objects"))
    {
        printError("GL init error", "GL_ARB_shader_objects extension was not found");
        return;
    }
	else
	{
		glActiveTexture			  = (PFNGLACTIVETEXTUREPROC)glfwGetProcAddress("glActiveTexture");
		glCreateProgramObjectARB  = (PFNGLCREATEPROGRAMOBJECTARBPROC)glfwGetProcAddress("glCreateProgramObjectARB");
		glDeleteObjectARB         = (PFNGLDELETEOBJECTARBPROC)glfwGetProcAddress("glDeleteObjectARB");
		glUseProgramObjectARB     = (PFNGLUSEPROGRAMOBJECTARBPROC)glfwGetProcAddress("glUseProgramObjectARB");
		glCreateShaderObjectARB   = (PFNGLCREATESHADEROBJECTARBPROC)glfwGetProcAddress("glCreateShaderObjectARB");
		glShaderSourceARB         = (PFNGLSHADERSOURCEARBPROC)glfwGetProcAddress("glShaderSourceARB");
		glCompileShaderARB        = (PFNGLCOMPILESHADERARBPROC)glfwGetProcAddress("glCompileShaderARB");
		glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC)glfwGetProcAddress("glGetObjectParameterivARB");
		glAttachObjectARB         = (PFNGLATTACHOBJECTARBPROC)glfwGetProcAddress("glAttachObjectARB");
		glGetInfoLogARB           = (PFNGLGETINFOLOGARBPROC)glfwGetProcAddress("glGetInfoLogARB");
		glLinkProgramARB          = (PFNGLLINKPROGRAMARBPROC)glfwGetProcAddress("glLinkProgramARB");
		glGetUniformLocationARB   = (PFNGLGETUNIFORMLOCATIONARBPROC)glfwGetProcAddress("glGetUniformLocationARB");
		glUniform4fARB            = (PFNGLUNIFORM4FARBPROC)glfwGetProcAddress("glUniform4fARB");
		glUniform3fARB            = (PFNGLUNIFORM3FARBPROC)glfwGetProcAddress("glUniform3fARB");
		glUniform1fARB            = (PFNGLUNIFORM1FARBPROC)glfwGetProcAddress("glUniform1fARB");
		glUniform1iARB            = (PFNGLUNIFORM1IARBPROC)glfwGetProcAddress("glUniform1iARB");

		// LOL holy sh-- is this tedious. 
		glUniformMatrix4fv            = (PFNGLUNIFORMMATRIX4FVPROC)glfwGetProcAddress("glUniformMatrix4fv");
		glBindAttribLocationARB				= (PFNGLBINDATTRIBLOCATIONARBPROC)glfwGetProcAddress("glBindAttribLocationARB");
		glGenVertexArrays					= (PFNGLGENVERTEXARRAYSPROC)glfwGetProcAddress("glGenVertexArrays");
		glBindVertexArray					= (PFNGLBINDVERTEXARRAYPROC)glfwGetProcAddress("glBindVertexArray");
		glDeleteVertexArrays				= (PFNGLDELETEVERTEXARRAYSPROC)glfwGetProcAddress("glDeleteVertexArrays");
		glGenBuffers						= (PFNGLGENBUFFERSARBPROC)glfwGetProcAddress("glGenBuffers");
		glBufferData						= (PFNGLBUFFERDATAARBPROC)glfwGetProcAddress("glBufferData");
		glBindBuffer						= (PFNGLBINDBUFFERARBPROC)glfwGetProcAddress("glBindBuffer");
		glBufferSubData						= (PFNGLBUFFERSUBDATAPROC)glfwGetProcAddress("glBufferSubData");
		glVertexAttribPointer				= (PFNGLVERTEXATTRIBPOINTERPROC)glfwGetProcAddress("glVertexAttribPointer");
		glEnableVertexAttribArray			= (PFNGLENABLEVERTEXATTRIBARRAYPROC)glfwGetProcAddress("glEnableVertexAttribArray");
		glUseProgram						= (PFNGLUSEPROGRAMPROC)glfwGetProcAddress("glUseProgram");
		glDisableVertexAttribArray			= (PFNGLDISABLEVERTEXATTRIBARRAYPROC)glfwGetProcAddress("glDisableVertexAttribArray");
		glDeleteProgram						= (PFNGLDELETEPROGRAMPROC)glfwGetProcAddress("glDeleteProgram");
		glDetachShader						= (PFNGLDETACHSHADERPROC)glfwGetProcAddress("glDetachShader");
		glDeleteBuffers						= (PFNGLDELETEBUFFERSPROC)glfwGetProcAddress("glDeleteBuffers");
		glDeleteShader						= (PFNGLDELETESHADERPROC)glfwGetProcAddress("glDeleteShader");
		glDrawRangeElements					= (PFNGLDRAWRANGEELEMENTSPROC)glfwGetProcAddress("glDrawRangeElements");

		glGenFramebuffersEXT					= (PFNGLGENFRAMEBUFFERSEXTPROC)glfwGetProcAddress("glGenFramebuffersEXT");
		glBindFramebufferEXT					= (PFNGLBINDFRAMEBUFFEREXTPROC)glfwGetProcAddress("glBindFramebufferEXT");
		glDeleteFramebuffersEXT					= (PFNGLDELETEFRAMEBUFFERSEXTPROC)glfwGetProcAddress("glDeleteFramebuffersEXT");
		glGenRenderbuffersEXT					= (PFNGLGENRENDERBUFFERSEXTPROC)glfwGetProcAddress("glGenRenderbuffersEXT");
		glRenderbufferStorageEXT					= (PFNGLRENDERBUFFERSTORAGEEXTPROC)glfwGetProcAddress("glRenderbufferStorageEXT");
		glBindRenderbufferEXT					= (PFNGLBINDRENDERBUFFEREXTPROC)glfwGetProcAddress("glBindRenderbufferEXT");
		glDeleteRenderbuffersEXT					= (PFNGLDELETERENDERBUFFERSEXTPROC)glfwGetProcAddress("glDeleteRenderbuffersEXT");
		glFramebufferTexture2DEXT					= (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)glfwGetProcAddress("glFramebufferTexture2DEXT");
		glFramebufferRenderbufferEXT					= (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)glfwGetProcAddress("glFramebufferRenderbufferEXT");
		glDrawBuffersARB					= (PFNGLDRAWBUFFERSARBPROC)glfwGetProcAddress("glDrawBuffersARB");
		glCheckFramebufferStatusEXT					= (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)glfwGetProcAddress("glCheckFramebufferStatusEXT");

		if( !glCreateProgramObjectARB || !glDeleteObjectARB || !glUseProgramObjectARB ||
			!glCreateShaderObjectARB || !glCreateShaderObjectARB || !glCompileShaderARB || 
			!glGetObjectParameterivARB || !glAttachObjectARB || !glGetInfoLogARB || 
			!glLinkProgramARB || !glGetUniformLocationARB || !glUniform4fARB ||
			!glUniform1fARB || !glUniform1iARB )
		{
			printError("GL init error", "One or more GL_ARB_shader_objects functions were not found");
			return;
		}
	}

	// Set the extensions to our classes.
	Texture::set_ptrGlActiveTexture( &glActiveTexture );
	Texture::set_ptrglUniform1i( &glUniform1iARB );
}

void Game::Dbg_render_with_fbo(){

	// set the rendering destination to FBO
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo[0]);

	// clear buffer
	glClearColor(0.0f,0.3f,0.6f,1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// draw geometry.
	gs_sceneStack->Submit_CurrentScene( *projview );

	// back to normal frame buffer.
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // unbind

	// unbind the color buffer.
	glBindTexture(GL_TEXTURE_2D, 0);
}
void Game::Dbg_render_normal(){
	// clear framebuffer
	glClearColor(0.0f,0.3f,0.6f,1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	ShaderPool::BindTextureToScreenBuffer(&tCB);

	Dbg_display_fsq();

	glBindTexture(GL_TEXTURE_2D, 0);
}

void Game::Dbg_display_fsq(){
	ShaderPool::SetForFSQ();
	gs_sceneStack->Draw_FSQ();
}

/*
 * showFPS() - Calculate and report frames per second
 * (updated once per second) in the window title bar
 */
void Game::showFPS() {

    double t, fps;
    
    // Get current time
    t = glfwGetTime();  // Gets number of seconds since glfwInit()
    // If one second has passed, or if this is the very first frame
	tElapsed = t-tLast;
	tLast = t;
	//printf("elapsed time: %f\n",tElapsed);
    if( (t - t0) > 1.0 || frames == 0 )
    {
        fps = (double)frames / (t - t0);
        sprintf(titlestring, "OpenGL Harness (%.1f FPS)", fps);
        glfwSetWindowTitle(titlestring);
        t0 = t;
        frames = 0;
    }
    frames ++;
}